﻿/*******************************************************************************
Copyright (c) 2011-2013, PKStudio
Copyright (c) 2011,      Zynga Inc.
Copyright (c) 2008-2010, Ricardo Quesada

http://pkstudio.org

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Use any of these editors to generate BMFonts:
	http://www.angelcode.com/products/bmfont/ (Free, Windows only)
	http://www.n4te.com/hiero/hiero.jnlp (Free, Java)
	http://slick.cokeandcode.com/demos/hiero.jnlp (Free, Java)
*******************************************************************************/

using System;
using System.Diagnostics;
using System.Drawing;
using System.IO;

namespace PKEngine
{
	/// <summary>
	/// PKLabelBmpFont is a sub class of PKNode that can render text with specified bitmap font.
	/// Each character is a PKSprite in PKLabelBmpFont.
	/// </summary>
	public class PKLabelBmpFont : PKNode, IPKLabel
	{
		#region Private Fields
		private string text;
		private PKLabelAlignment alignment;
		private int charSpacing;
		private int lineSpacing;
		private int realOpacity;
		private ushort realColor;
		private int realColorOpacity;
		private PKSpriteDisplayMode displayMode = PKSpriteDisplayMode.Default;
		#endregion

		#region Public Properties
		public PKBmpFontConfig BmpFontConfig { get; private set; }
		public PKSurface Surface { get; private set; }

		public string Text
		{
			get
			{
				return this.text;
			}
			set
			{
				if (this.text != value)
				{
					this.text = value;
					this.CreateCharSpritesHelper();
				}
			}
		}

		public PKLabelAlignment Alignment
		{
			get
			{
				return this.alignment;
			}
			set
			{
				this.alignment = value;
				this.CreateCharSpritesHelper();
			}
		}

		public int CharSpacing
		{
			get
			{
				return this.charSpacing;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				this.charSpacing = value;
				this.CreateCharSpritesHelper();
			}
		}

		public int LineSpacing
		{
			get
			{
				return this.lineSpacing;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				this.lineSpacing = value;
				this.CreateCharSpritesHelper();
			}
		}

		public int Opacity
		{
			get
			{
				return this.realOpacity;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				else if (value > 256)
				{
					value = 256;
				}
				this.DisplayedOpacity = this.realOpacity = value;

				if (this.CascadeOpacityEnabled)
				{
					int parentOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeOpacityEnabled)
					{
						parentOpacity = ((IPKRgba)this.Parent).DisplayedOpacity;
					}
					this.UpdateDisplayedOpacity(parentOpacity);
				}
			}
		}

		public int ColorOpacity
		{
			get
			{
				return this.realColorOpacity;
			}
			set
			{
				if (value < 0)
				{
					value = 0;
				}
				else if (value > 256)
				{
					value = 256;
				}
				this.DisplayedColorOpacity = this.realColorOpacity = value;

				if (this.CascadeColorEnabled)
				{
					ushort parentColor = PKAlpha.SharedAlpha.ColorWhite;
					int parentColorOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeColorEnabled)
					{
						parentColor = ((IPKRgba)this.Parent).DisplayedColor;
						parentColorOpacity = ((IPKRgba)this.Parent).DisplayedColorOpacity;
					}
					this.UpdateDisplayedColor(parentColor, parentColorOpacity);
				}
			}
		}

		public ushort Color
		{
			get
			{
				return this.realColor;
			}
			set
			{
				this.DisplayedColor = this.realColor = value;

				if (this.CascadeColorEnabled)
				{
					ushort parentColor = PKAlpha.SharedAlpha.ColorWhite;
					int parentColorOpacity = 256;
					if (this.Parent != null && this.Parent is IPKRgba && ((IPKRgba)this.Parent).CascadeColorEnabled)
					{
						parentColor = ((IPKRgba)this.Parent).DisplayedColor;
						parentColorOpacity = ((IPKRgba)this.Parent).DisplayedColorOpacity;
					}
					this.UpdateDisplayedColor(parentColor, parentColorOpacity);
				}
			}
		}

		/// <summary>
		/// Displayed Opacity.
		/// </summary>
		public int DisplayedOpacity { get; private set; }

		/// <summary>
		/// Displayed ColorOpacity.
		/// </summary>
		public int DisplayedColorOpacity { get; private set; }

		/// <summary>
		/// Displayed Color.
		/// </summary>
		public ushort DisplayedColor { get; private set; }

		/// <summary>
		/// Whether or not opacity should be propagated to its children.
		/// </summary>
		public bool CascadeOpacityEnabled { get; set; }

		/// <summary>
		/// Whether or not color should be propagated to its children.
		/// </summary>
		public bool CascadeColorEnabled { get; set; }

		public PKSpriteDisplayMode DisplayMode
		{
			get
			{
				return this.displayMode;
			}
			set
			{
				this.displayMode = value;
				foreach (PKNode node in Children)
				{
					PKSprite sprite = node as PKSprite;
					if (sprite != null)
					{
						sprite.DisplayMode = value;
					}
				}
			}
		}
		#endregion

		#region Initialization
		public static PKLabelBmpFont LabelMake(string text, string fntFile)
		{
			return new PKLabelBmpFont().Init(text, fntFile);
		}

		public static PKLabelBmpFont LabelMake(string text, string fntFile, PKLabelAlignment alignment)
		{
			return new PKLabelBmpFont().Init(text, fntFile, alignment);
		}

		public PKLabelBmpFont Init(string text, string fntFile)
		{
			return this.Init(text, fntFile, PKLabelAlignment.Default);
		}

		public PKLabelBmpFont Init(string text, string fntFile, PKLabelAlignment alignment)
		{
			Debug.Assert(!string.IsNullOrEmpty(fntFile), "PKLabelBmpFont: Argument cannot be null.");
			Debug.Assert(File.Exists(fntFile), "PKLabelBmpFont: Cannot find file.");

			this.DisplayedColor = this.Color = PKAlpha.SharedAlpha.ColorWhite;
			this.DisplayedOpacity = this.Opacity = this.DisplayedColorOpacity = this.ColorOpacity = 256;
			this.CascadeOpacityEnabled = this.CascadeColorEnabled = false;

			this.BmpFontConfig = PKBmpFontCache.SharedFontCache.AddFont(fntFile);
			this.Alignment = alignment;
			this.CharSpacing = 0;
			this.LineSpacing = 0;
			this.Surface = PKSurfaceCache.SharedSurfaceCache.AddImage(this.BmpFontConfig.Name);

			// Create char sprites.
			this.Text = text;

			return this;
		}
		#endregion

		#region IPKRgba Methods
		/// <summary>
		/// Recursive method that updates display opacity.
		/// </summary>
		/// <param name="parentOpacity"></param>
		public void UpdateDisplayedOpacity(int parentOpacity)
		{
			this.DisplayedOpacity = realOpacity * parentOpacity / 256;

			if (this.CascadeOpacityEnabled)
			{
				foreach (PKNode child in this.Children)
				{
					if (child is IPKRgba)
					{
						((IPKRgba)child).UpdateDisplayedOpacity(this.DisplayedOpacity);
					}
				}
			}
		}

		/// <summary>
		/// Recursive method that updates display color.
		/// </summary>
		public void UpdateDisplayedColor(ushort parentColor, int parentColorOpacity)
		{
			this.DisplayedColorOpacity = parentColorOpacity + realColorOpacity * (256 - parentColorOpacity) / 256;
			this.DisplayedColor = PKAlpha.SharedAlpha.PixelAlpha(this.realColor, parentColor, realColorOpacity);

			if (this.CascadeColorEnabled)
			{
				foreach (PKNode child in this.Children)
				{
					if (child is IPKRgba)
					{
						((IPKRgba)child).UpdateDisplayedColor(this.DisplayedColor, this.DisplayedColorOpacity);
					}
				}
			}
		}
		#endregion

		#region Private Methods
		private void CreateCharSpritesHelper()
		{
			this.ClearChildren(true);

			if (this.BmpFontConfig != null && this.BmpFontConfig.FontDefDict != null && this.Surface != null)
			{
				string[] lines = this.Text.Split(new char[] { '\n' });

				int maxWidth = 0;
				foreach (string line in lines)
				{
					int width = this.BmpFontConfig.MeasureString(line, this.CharSpacing);
					if (width > maxWidth) { maxWidth = width; }
				}
				int maxHeight = (this.BmpFontConfig.CommonHeight + this.LineSpacing) * lines.Length;
				if (maxHeight > 0)
				{
					maxHeight -= this.LineSpacing;
				}
				this.ContentSize = new Size(maxWidth, maxHeight);

				int nextX = 0;
				int nextY = 0;
				ushort current;
				ushort prev = 0xffff;
				int kerningAmount = 0;
				foreach (string line in lines)
				{
					switch (this.Alignment)
					{
						case PKLabelAlignment.Left:
							nextX = 0;
							break;
						case PKLabelAlignment.Center:
							nextX = (maxWidth - this.BmpFontConfig.MeasureString(line, this.CharSpacing)) / 2;
							break;
						case PKLabelAlignment.Right:
							nextX = maxWidth - this.BmpFontConfig.MeasureString(line, this.CharSpacing);
							break;
					}
					foreach (char ch in line)
					{
						if (!this.BmpFontConfig.CharacterList.Contains(ch))
						{
							Debug.Print("PKLabelBmpFont.CreateCharSpritesHelper: Characer not found.");
							continue;
						}

						current = (ushort)ch;
						kerningAmount = this.BmpFontConfig.GetKerningAmount(prev, current);

						PKBmpFontDef fontDef = this.BmpFontConfig.FontDefDict[current];
						PKSprite charSprite = new PKSprite(this.Surface, fontDef.Rect);
						charSprite.Position = new Point(nextX + fontDef.XOffset + kerningAmount, nextY + fontDef.YOffset);
						charSprite.DisplayMode = this.displayMode;
						charSprite.Opacity = this.DisplayedOpacity;
						charSprite.Color = this.DisplayedColor;
						charSprite.ColorOpacity = this.DisplayedColorOpacity;
						this.AddChild(charSprite);

						nextX += this.BmpFontConfig.FontDefDict[current].XAdvance + kerningAmount + this.charSpacing;
						prev = current;
					}
					nextY += this.BmpFontConfig.CommonHeight + this.LineSpacing;
				}
			}
		}
		#endregion
	}
}
